热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux|多线程与fork

经由fork()创建的子进程,其中只有一个线程。子进程里仅存的线程,对应着主进程里调用fork()函数的线程。此外,主进程的整个虚存空间都

经由 fork() 创建的子进程,其中只有一个线程。子进程里仅存的线程,对应着主进程里调用 fork() 函数的线程。此外,主进程的整个虚存空间都被复制到了子进程。因而,包括互斥锁、条件变量、其余线程的内部对象等,都保持着原样。由此引发的问题,可以考虑用 pthread_atfork() 函数解决。
参考自:https://liam.page/2017/01/17/fork-safe/

1.线程内fork产生的子进程,只会执行该线程部分(主进程不会向下执行)

#include
#include
#include
#include
#include
#include
/* 功能说明:* 主函数 ---- ------main:主线程---> 打印:main:主线程id=..* 工作线程 --fork() ---fun:父进程---> 打印: fun:父进程id=.. * |---fun:子进程---> 打印: fun: 子进程id=..*/void *Thread_fun()
{pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}else{for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}
}int main()
{pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);exit(0);
}

在这里插入图片描述
fork产生的子进程中只有一个线程。

执行结果&#xff1a;
main::主线程 【1】:id &#61; 74226
fun::父进程 【1】:id &#61; 74226
fun::子进程 【1】:id &#61; 74228
fun::父进程 【2】:id &#61; 74226
main::主线程 【2】:id &#61; 74226
fun::子进程 【2】:id &#61; 74228
fun::父进程 【3】:id &#61; 74226
main::主线程 【3】:id &#61; 74226
fun::子进程 【3】:id &#61; 74228
main is stop …

2.尝试加锁控制执行状态

如果我们把输出屏幕看成一块共享资源的话&#xff0c;我们想要输出结果为&#xff0c;先让main中的打印完成&#xff0c;在打印线程内的。则我们需要加锁完成。

2.1 fork复制了main中锁状态&#xff0c;则线程内的子进程部分加锁。效果不理想

把临界区代码加锁。&#xff08;需要注意的是&#xff0c;fork会复制父进程加锁状态&#xff09;&#xff0c;如果我们代码这样写。

include <stdio.h>
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁
/* 功能说明&#xff1a;* 主函数 ---- ------main:主线程---> 打印&#xff1a;main&#xff1a;主线程id&#61;..* 工作线程 --fork() ---fun&#xff1a;父进程---> 打印&#xff1a; fun&#xff1a;父进程id&#61;.. * |---fun&#xff1a;子进程---> 打印&#xff1a; fun: 子进程id&#61;..** 加锁&#xff1a;保证先让main中的打印先完成*/void *Thread_fun()
{sleep(1); // 保证让主进程先获得锁pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{// 注意&#xff1a;这里没加锁&#xff0c;因为子进程复制的父进程此时是处于加锁状态的&#xff0c;如果这里加锁会产生死锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}

执行结果&#xff1a;
main::主线程 【m 1】:id &#61; 75080
main::主线程 【m 2】:id &#61; 75080
fun::子进程 【f_c1】:id &#61; 75082
main::主线程 【m 3】:id &#61; 75080
fun::子进程 【f_c2】:id &#61; 75082
fun::子进程 【f_c3】:id &#61; 75082
main is stop …
fun::父进程 【f_f1】:id &#61; 75080
fun::父进程 【f_f2】:id &#61; 75080
fun::父进程 【f_f3】:id &#61; 75080

可以看到&#xff0c;在父进程中&#xff0c;遵循先答应main的内容&#xff0c;在打印线程函数。&#xff08;main is stop … 之后才执行fun::父进程&#xff09;。但是由于子进程中临界区没有加锁&#xff0c;子进程的输出不受限制&#xff0c;干扰了主进程中的main的输出。

2.1 fork复制了main中锁状态&#xff0c;则线程内的子进程部分加锁。产生死锁&#xff0c;子进程一直阻塞

如果我们给子进程中的临界区加锁又会产生另一种问题。&#xff08;死锁&#xff09;以下代码仅修改了子进程的临界区进行加锁操作。

#include
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁
/* 功能说明&#xff1a;* 主函数 ---- ------main:主线程---> 打印&#xff1a;main&#xff1a;主线程id&#61;..* 工作线程 --fork() ---fun&#xff1a;父进程---> 打印&#xff1a; fun&#xff1a;父进程id&#61;.. * |---fun&#xff1a;子进程---> 打印&#xff1a; fun: 子进程id&#61;..** 在工作线程中fork&#xff0c;产生的子进程&#xff0c;只会执行工作线程内的代码。但子进程会复制父进程的状态&#xff0c;如加锁。*** 加锁&#xff1a;保证先让main中的打印先完成* 问题&#xff1a;fork会复制父进程中的加锁状态&#xff0c;即在工作线程中&#xff0c;子进程已经是处于加锁状态的&#xff0c;再次加锁无法完成&#xff0c;则产生死锁。*/// 注意&#xff1a;以下代码子进程会产生死锁
void *Thread_fun()
{sleep(1); // 保证让主进程先获得锁pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{/* 这里产生死锁&#xff0c;复制的父进程中加锁状态,* 但是线程内使用fork不会再执行父进程中的代码&#xff0c;因此..*/pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}

输出结果&#xff1a;
main::主线程 【m 1】:id &#61; 75247
main::主线程 【m 2】:id &#61; 75247
main::主线程 【m 3】:id &#61; 75247
main is stop …
fun::父进程 【f_f1】:id &#61; 75247
fun::父进程 【f_f2】:id &#61; 75247
fun::父进程 【f_f3】:id &#61; 75247

2.3.分析阻塞原因

可以看到子进程中&#xff0c;并没有执行打印操作。使用ps查看&#xff0c;发现有进程还未退出&#xff0c;正是我们的子进程。
在这里插入图片描述
父进程执行过程大致如下&#xff1a;
在这里插入图片描述
子进程中&#xff0c;因为工作线程以外的部分不继续执行了&#xff0c;则工作线程一直处于阻塞状态。
在这里插入图片描述

2.4 对fork加锁&#xff08;试探锁&#xff0c;试探main中是否已经解锁&#xff09;

解决方案&#xff1a;
确保fork()时&#xff0c;父进程是没有加锁的。因为本程序需要实现先打印main的内容&#xff0c;因此我们可以这样做&#x1f447;&#xff0c;确保main中先打印并且不会占用占用锁资源。

// 尝试加锁&#xff0c;如果可以加锁&#xff0c;则证明父进程没有占用锁资源
pid_t pid &#61; fork();
// 解锁

代码如下&#xff1a;

#include
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁void *Thread_fun()
{pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证临界区锁以释放&#xff0c;不会被fork复制到子进程中阻塞临界区pid_t pid &#61; fork();pthread_mutex_unlock(&mutex); // 解锁if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}

输出结果&#xff1a;
main::主线程 【m 1】:id &#61; 75807
main::主线程 【m 2】:id &#61; 75807
main::主线程 【m 3】:id &#61; 75807
main is stop …
fun::父进程 【f_f1】:id &#61; 75807
fun::子进程 【f_c1】:id &#61; 75811
fun::子进程 【f_c2】:id &#61; 75811
fun::父进程 【f_f2】:id &#61; 75807
fun::子进程 【f_c3】:id &#61; 75811
fun::父进程 【f_f3】:id &#61; 75807

3.系统提供的解决方案 pthread_atfork

如果我们有很多不同的锁&#xff0c;按照我们之前的解决方案&#xff0c;我们需要在 fork 之前进行 n 多次的试探动作&#xff0c;不仅麻烦还极容易引起死锁。而系统为我们提供了一个函数可以解决这个问题。

#include int pthread_atfork (void (*prepare) (void), void (*parent) (void), void (*child) (void));#include
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁
pthread_mutex_t lock; // 创建互斥锁void prepare() {pthread_mutex_lock(&mutex); // 加锁printf("prepare: 加锁 ...\n");
}void parent() {pthread_mutex_unlock(&mutex);printf("parent : 父进程解锁 ...\n");
}void child() {pthread_mutex_unlock(&mutex);printf("child : 子进程解锁 ...\n");
}void *Thread_fun(void*arg)
{sleep(1);pthread_mutex_lock(&lock); // 加锁for(int i &#61; 0; i < 5; i&#43;&#43;){printf(" fun::%s 【f_f%d】:id &#61; %d\n",(char*)arg,i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&lock); // 解锁
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_mutex_init(&lock,NULL);pthread_t id; // 创建线程并调用工作线程// 注册forkint res &#61; pthread_atfork(prepare, parent, child);assert(res &#61;&#61; 0);char buff[] &#61; "父进程";int ret &#61; pthread_create(&id,NULL,Thread_fun,(void*)buff);assert(ret &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功/* fork调用前调用preoarefork调用完&#xff0c;在调用child和parent*/pid_t pid &#61; fork();/*在父进程中&#xff08;当前进程中&#xff09;调用fork。经由 fork() 创建的子进程&#xff0c;其中只有一个线程&#xff08;调用fork的线程&#xff0c;即主线程&#xff09;因此&#xff0c;fork产生的子进程中&#xff0c;不会执行Thread_fun()函数&#xff0c;则我们可以在父进程&#xff08;当前进程中手动调用&#xff09;*/if(pid &#61;&#61; 0) // 子进程{sleep(1); // 保证main中父进程先执行// 注&#xff1a;这里的子进程与父进程是两个进程&#xff0c;不共享锁。同步需要设置管道或者信号量等 // ********strcpy(buff,"子进程");Thread_fun((void*)buff);}else // 父进程{pthread_mutex_lock(&lock); // 加锁for(int i &#61; 0; i < 10; i&#43;&#43;){printf("main::主函数 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&lock); // 解锁}// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);pthread_mutex_destroy(&lock);exit(0);
}

输出&#xff1a;
父进程&#xff1a;先输出 main中的&#xff0c;再输出工作线程中的
子进程&#xff1a;只有一个线程&#xff0c;在main中&#xff0c;通过手动调用执行工作函数。由于进程间全局变量&#xff08;我们定义的锁&#xff09;不共享&#xff0c;所以子进程在输出时不受锁影响。如有需要加入型号量等。。


推荐阅读
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • 本题通过将每个矩形视为一个节点,根据其相对位置构建拓扑图,并利用深度优先搜索(DFS)或状态压缩动态规划(DP)求解最小涂色次数。本文详细解析了该问题的建模思路与算法实现。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • Java 中 Writer flush()方法,示例 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • Splay Tree 区间操作优化
    本文详细介绍了使用Splay Tree进行区间操作的实现方法,包括插入、删除、修改、翻转和求和等操作。通过这些操作,可以高效地处理动态序列问题,并且代码实现具有一定的挑战性,有助于编程能力的提升。 ... [详细]
  • 本教程涵盖OpenGL基础操作及直线光栅化技术,包括点的绘制、简单图形绘制、直线绘制以及DDA和中点画线算法。通过逐步实践,帮助读者掌握OpenGL的基本使用方法。 ... [详细]
  • 本题涉及一棵由N个节点组成的树(共有N-1条边),初始时所有节点均为白色。题目要求处理两种操作:一是改变某个节点的颜色(从白变黑或从黑变白);二是查询从根节点到指定节点路径上的第一个黑色节点,若无则输出-1。 ... [详细]
  • Codeforces Round #566 (Div. 2) A~F个人题解
    Dashboard-CodeforcesRound#566(Div.2)-CodeforcesA.FillingShapes题意:给你一个的表格,你 ... [详细]
author-avatar
手机用户2502885897
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有